# =============================================================================
# SCRAP_X_BUKELE_FINAL.R — v3.0 (todos los bugs resueltos)
# Script de recolección de tweets de @nayibbukele en X/Twitter
# Proyecto: Corpus para investigación sobre erosión democrática en América Latina
# =============================================================================

setwd("TU_RUTA_AQUI")  # Ejemplo: "~/proyectos/BUKELE"

library(reticulate)
library(readr)
library(dplyr)
library(lubridate)
library(stringr)

use_virtualenv("~/playwright-env", required = TRUE)
py_run_file("scraper.py")

# =============================================================================
# PARÁMETROS
# =============================================================================

AUTH_TOKEN <- "TU_AUTH_TOKEN_AQUI"

CONFIG <- list(
  modo_headless     = FALSE,
  timeout_pagina    = 60000,
  timeout_elemento  = 10000,
  scroll_pixels     = 1500,
  delay_min         = 2.0,
  delay_max         = 4.0
)

USUARIO                <- "nayibbukele"
UMBRAL_SATURACION      <- 18
ARCHIVO_CONSOLIDADO    <- "tweets_bukele_COMPLETO.csv"
ARCHIVO_DIAS_SATURADOS <- "dias_saturados_bukele.csv"

PERIODOS <- list(
  P0 = list(
    nombre   = "P0_prepresidencia",
    etiqueta = "Pre-presidencia (Alcaldía + campaña)",
    inicio   = as.Date("2012-01-08"),
    fin      = as.Date("2019-05-31"),
    archivo  = "tweets_bukele_P0_prepresidencia.csv"
  ),
  P1 = list(
    nombre   = "P1_presidencia1",
    etiqueta = "Presidencia 1",
    inicio   = as.Date("2019-06-01"),
    fin      = as.Date("2024-05-31"),
    archivo  = "tweets_bukele_P1_presidencia1.csv"
  ),
  P2 = list(
    nombre   = "P2_presidencia2",
    etiqueta = "Presidencia 2",
    inicio   = as.Date("2024-06-01"),
    fin      = Sys.Date(),
    archivo  = "tweets_bukele_P2_presidencia2.csv"
  )
)

# =============================================================================
# AUXILIARES
# =============================================================================
`%||%` <- function(x, y) if (!is.null(x) && length(x) > 0) x else y

# Lee CSV garantizando tipos correctos
leer_csv_seguro <- function(archivo) {
  read_csv(archivo, show_col_types = FALSE,
           col_types = cols(
             id           = col_character(),
             fecha        = col_character(),
             texto        = col_character(),
             likes        = col_integer(),
             retweets     = col_integer(),
             replies      = col_integer(),
             url          = col_character(),
             es_retweet   = col_logical(),
             es_respuesta = col_logical(),
             periodo      = col_character(),
             .default     = col_character()
           ))
}

tweets_a_df <- function(tweets_raw, periodo_nombre = "") {
  if (length(tweets_raw) == 0) return(data.frame())
  bind_rows(lapply(tweets_raw, function(t) {
    data.frame(
      id           = as.character(t$id           %||% ""),
      fecha        = as.character(t$fecha        %||% ""),
      texto        = as.character(t$texto        %||% ""),
      likes        = as.integer( t$likes        %||% 0L),
      retweets     = as.integer( t$retweets     %||% 0L),
      replies      = as.integer( t$replies      %||% 0L),
      url          = as.character(t$url          %||% ""),
      es_retweet   = as.logical( t$es_retweet   %||% FALSE),
      es_respuesta = as.logical( t$es_respuesta %||% FALSE),
      periodo      = as.character(periodo_nombre),
      stringsAsFactors = FALSE
    )
  }))
}

url_busqueda_diaria <- function(usuario, desde, hasta) {
  hasta_mas1 <- format(as.Date(hasta) + 1, "%Y-%m-%d")
  paste0(
    "https://x.com/search?q=(from%3A", usuario, ")",
    "%20until%3A", hasta_mas1,
    "%20since%3A", desde,
    "&src=typed_query&f=live"
  )
}

url_busqueda_horaria <- function(usuario, fecha, hora_inicio_utc, hora_fin_utc) {
  since <- sprintf("%s_%02d:00:00_UTC", fecha, hora_inicio_utc)
  until <- sprintf("%s_%02d:00:00_UTC", fecha, hora_fin_utc)
  paste0(
    "https://x.com/search?q=(from%3A", usuario, ")",
    "%20until%3A", until,
    "%20since%3A", since,
    "&src=typed_query&f=live"
  )
}

scrape_url_periodo <- function(url, ids_vistos, periodo_nombre, verbose = TRUE) {
  tweets_raw <- tryCatch({
    py$scrape_url_sesion(url, ids_vistos)
  }, error = function(e) {
    if (verbose) cat(sprintf("    [ERROR] %s\n", conditionMessage(e)))
    list()
  })
  
  if (length(tweets_raw) == 0) return(data.frame())
  
  tweets_a_df(tweets_raw, periodo_nombre) %>%
    filter(!id %in% ids_vistos, !es_retweet)
}

# =============================================================================
# FUNCIÓN PRINCIPAL
# =============================================================================
procesar_periodo <- function(periodo) {
  
  # Guardar nombre en variable local para evitar colisión con columna "periodo"
  periodo_nombre_str <- periodo$nombre
  periodo_etiqueta   <- periodo$etiqueta
  periodo_inicio     <- periodo$inicio
  periodo_fin        <- periodo$fin
  periodo_archivo    <- periodo$archivo
  
  cat(sprintf("\n%s\n", strrep("═", 60)))
  cat(sprintf("  PERÍODO: %s\n", periodo_etiqueta))
  cat(sprintf("  Rango  : %s → %s\n",
              format(periodo_inicio, "%d %b %Y"),
              format(periodo_fin,    "%d %b %Y")))
  cat(sprintf("%s\n\n", strrep("═", 60)))
  
  # Cargar progreso previo
  if (file.exists(periodo_archivo)) {
    df_acumulado <- leer_csv_seguro(periodo_archivo)
    cat(sprintf("✓ Progreso previo cargado: %d tweets\n", nrow(df_acumulado)))
  } else {
    df_acumulado <- data.frame()
    cat("✓ Iniciando período desde cero\n")
  }
  
  ids_vistos <- if (nrow(df_acumulado) > 0) as.character(df_acumulado$id) else character(0)
  
  # Cargar log de días saturados
  if (file.exists(ARCHIVO_DIAS_SATURADOS)) {
    dias_sat_log <- read_csv(ARCHIVO_DIAS_SATURADOS, show_col_types = FALSE,
                             col_types = cols(
                               fecha      = col_character(),
                               periodo    = col_character(),
                               tweets_dia = col_integer()
                             ))
  } else {
    dias_sat_log <- data.frame(fecha = character(0), periodo = character(0),
                               tweets_dia = integer(0))
  }
  
  dias_saturados_nuevos <- character(0)
  
  # ── FASE 1: Barrido diario ─────────────────────────────────────────────────
  cat(sprintf("\n--- FASE 1: Barrido diario ---\n"))
  dias <- seq(periodo_inicio, periodo_fin, by = "day")
  cat(sprintf("Total días a procesar: %d\n\n", length(dias)))
  
  for (i in seq_along(dias)) {
    fecha_str <- format(dias[i], "%Y-%m-%d")
    
    if (i %% 100 == 0 || i == 1) {
      cat(sprintf("[%d/%d] %s | Acumulado: %d tweets\n",
                  i, length(dias), fecha_str, length(ids_vistos)))
    }
    
    url      <- url_busqueda_diaria(USUARIO, fecha_str, fecha_str)
    df_nuevo <- scrape_url_periodo(url, ids_vistos, periodo_nombre_str, verbose = FALSE)
    
    if (nrow(df_nuevo) > 0) {
      if (nrow(df_nuevo) >= UMBRAL_SATURACION) {
        dias_saturados_nuevos <- c(dias_saturados_nuevos, fecha_str)
        cat(sprintf("  ⚠️  DÍA SATURADO: %s (%d tweets)\n", fecha_str, nrow(df_nuevo)))
      }
      
      df_acumulado <- bind_rows(df_acumulado, df_nuevo) %>%
        distinct(id, .keep_all = TRUE)
      ids_vistos <- as.character(df_acumulado$id)
      write_csv(df_acumulado, periodo_archivo)
      
      cat(sprintf("  +%d | %s | Total: %d\n",
                  nrow(df_nuevo), fecha_str, nrow(df_acumulado)))
    }
    
    Sys.sleep(runif(1, 0.5, 1.0))
  }
  
  cat(sprintf("\n✓ Fase 1 completada. Total: %d tweets\n", nrow(df_acumulado)))
  
  # Guardar días saturados
  if (length(dias_saturados_nuevos) > 0) {
    nuevos_sat <- data.frame(
      fecha      = dias_saturados_nuevos,
      periodo    = periodo_nombre_str,
      tweets_dia = NA_integer_
    )
    dias_sat_log <- bind_rows(dias_sat_log, nuevos_sat) %>%
      distinct(fecha, .keep_all = TRUE)
    write_csv(dias_sat_log, ARCHIVO_DIAS_SATURADOS)
  }
  
  # ── FASE 2: Re-búsqueda horaria ────────────────────────────────────────────
  dias_sat_periodo <- dias_sat_log %>%
    filter(periodo == periodo_nombre_str) %>%
    pull(fecha)
  
  if (length(dias_sat_periodo) > 0) {
    cat(sprintf("\n--- FASE 2: Re-búsqueda horaria (%d días saturados) ---\n",
                length(dias_sat_periodo)))
    
    franjas_utc <- list(
      list(inicio = 0,  fin = 6),
      list(inicio = 6,  fin = 12),
      list(inicio = 12, fin = 18),
      list(inicio = 18, fin = 24)
    )
    
    for (fecha_sat in dias_sat_periodo) {
      cat(sprintf("  %s:", fecha_sat))
      nuevos_dia <- 0
      
      for (franja in franjas_utc) {
        url      <- url_busqueda_horaria(USUARIO, fecha_sat, franja$inicio, franja$fin)
        df_nuevo <- scrape_url_periodo(url, ids_vistos, periodo_nombre_str, verbose = FALSE)
        
        if (nrow(df_nuevo) > 0) {
          if (nrow(df_nuevo) >= UMBRAL_SATURACION) {
            cat(sprintf("\n    ⚠️  FRANJA SATURADA: %02d:00-%02d:00 UTC\n",
                        franja$inicio, franja$fin))
          }
          df_acumulado <- bind_rows(df_acumulado, df_nuevo) %>%
            distinct(id, .keep_all = TRUE)
          ids_vistos <- as.character(df_acumulado$id)
          nuevos_dia <- nuevos_dia + nrow(df_nuevo)
        }
        
        Sys.sleep(runif(1, 1, 2))
      }
      
      if (nuevos_dia > 0) {
        write_csv(df_acumulado, periodo_archivo)
        cat(sprintf(" +%d recuperados | Total: %d\n", nuevos_dia, nrow(df_acumulado)))
      } else {
        cat(" sin adicionales\n")
      }
    }
  } else {
    cat("\n--- FASE 2: Sin días saturados ---\n")
  }
  
  # ── FASE 3: Verificación en períodos críticos ──────────────────────────────
  cat(sprintf("\n--- FASE 3: Verificación de días en blanco en períodos críticos ---\n"))
  
  periodos_criticos <- list(
    list(inicio = "2021-02-01", fin = "2021-03-15", desc = "Irrupción al Congreso"),
    list(inicio = "2021-06-01", fin = "2021-06-30", desc = "Ley Bitcoin"),
    list(inicio = "2021-09-01", fin = "2021-09-30", desc = "Bitcoin moneda oficial"),
    list(inicio = "2022-03-01", fin = "2022-04-30", desc = "Estado de excepción"),
    list(inicio = "2023-01-01", fin = "2023-12-31", desc = "Campaña reelección"),
    list(inicio = "2024-02-01", fin = "2024-02-29", desc = "Reelección presidencial")
  )
  
  criticos_en_periodo <- Filter(function(p) {
    p$fin >= as.character(periodo_inicio) && p$inicio <= as.character(periodo_fin)
  }, periodos_criticos)
  
  if (length(criticos_en_periodo) == 0 || nrow(df_acumulado) == 0) {
    cat("  Sin períodos críticos en este rango.\n")
  } else {
    tweets_con_fecha <- df_acumulado %>%
      filter(!is.na(fecha), fecha != "") %>%
      mutate(fecha_solo = as.Date(substr(fecha, 1, 10)))
    
    for (pc in criticos_en_periodo) {
      inicio_pc <- max(as.Date(pc$inicio), periodo_inicio)
      fin_pc    <- min(as.Date(pc$fin),    periodo_fin)
      if (inicio_pc > fin_pc) next
      
      dias_criticos  <- seq(inicio_pc, fin_pc, by = "day")
      dias_con_datos <- unique(as.character(tweets_con_fecha %>%
                                              filter(fecha_solo >= inicio_pc, fecha_solo <= fin_pc) %>%
                                              pull(fecha_solo)))
      
      dias_en_blanco <- setdiff(as.character(dias_criticos), dias_con_datos)
      
      if (length(dias_en_blanco) > 0) {
        cat(sprintf("  [%s] %d días en blanco — verificando...\n",
                    pc$desc, length(dias_en_blanco)))
        
        for (fecha_blanco in dias_en_blanco) {
          url      <- url_busqueda_diaria(USUARIO, fecha_blanco, fecha_blanco)
          df_nuevo <- scrape_url_periodo(url, ids_vistos, periodo_nombre_str, verbose = FALSE)
          
          if (nrow(df_nuevo) > 0) {
            df_acumulado <- bind_rows(df_acumulado, df_nuevo) %>%
              distinct(id, .keep_all = TRUE)
            ids_vistos <- as.character(df_acumulado$id)
            write_csv(df_acumulado, periodo_archivo)
            cat(sprintf("    %s: +%d recuperados\n", fecha_blanco, nrow(df_nuevo)))
          }
          
          Sys.sleep(runif(1, 1, 2))
        }
      } else {
        cat(sprintf("  ✓ [%s]: sin días en blanco\n", pc$desc))
      }
    }
  }
  
  # ── Enriquecimiento final ──────────────────────────────────────────────────
  df_enriquecido <- df_acumulado %>%
    filter(!is.na(id), id != "") %>%
    mutate(id = as.character(id)) %>%
    distinct(id, .keep_all = TRUE) %>%
    filter(!es_retweet) %>%
    mutate(
      fecha_dt    = ymd_hms(fecha, quiet = TRUE),
      fecha_local = with_tz(fecha_dt, "America/El_Salvador"),
      texto       = str_squish(texto),
      es_retweet   = str_starts(texto, fixed("RT @")),
      es_respuesta = !es_retweet & str_starts(texto, fixed("@")),
      tipo_tweet   = case_when(
        es_retweet   ~ "retweet",
        es_respuesta ~ "respuesta",
        TRUE         ~ "original"
      ),
      periodo = periodo_nombre_str
    ) %>%
    filter(!es_retweet) %>%
    arrange(fecha_dt)
  
  write_csv(df_enriquecido, periodo_archivo)
  
  cat(sprintf("\n┌─────────────────────────────────────────────┐\n"))
  cat(sprintf("│ RESUMEN: %s\n", periodo_etiqueta))
  cat(sprintf("│ Tweets         : %s\n", format(nrow(df_enriquecido), big.mark = ",")))
  cat(sprintf("│   Originales   : %s\n", format(sum(df_enriquecido$tipo_tweet == "original"), big.mark = ",")))
  cat(sprintf("│   Respuestas   : %s\n", format(sum(df_enriquecido$es_respuesta), big.mark = ",")))
  cat(sprintf("│ Archivo        : %s\n", periodo_archivo))
  cat(sprintf("└─────────────────────────────────────────────┘\n"))
  
  return(df_enriquecido)
}

# =============================================================================
# EJECUCIÓN PRINCIPAL
# =============================================================================

cat("\n╔══════════════════════════════════════════════════════════╗\n")
cat("║   CORPUS @nayibbukele — INICIO DE RECOLECCIÓN  v3.0    ║\n")
cat("╚══════════════════════════════════════════════════════════╝\n")
cat(sprintf("Fecha   : %s\n", format(Sys.time(), "%Y-%m-%d %H:%M:%S")))
cat(sprintf("Usuario : @%s\n\n", USUARIO))

cat("Iniciando sesión en X (se abrirá Chrome una sola vez)...\n")
py$iniciar_sesion(AUTH_TOKEN, CONFIG)
cat("✓ Navegador listo. Comenzando scraping...\n")

df_P0 <- procesar_periodo(PERIODOS$P0)
df_P1 <- procesar_periodo(PERIODOS$P1)
df_P2 <- procesar_periodo(PERIODOS$P2)

py$cerrar_sesion()

cat("\n=== Consolidación final ===\n")

df_consolidado <- bind_rows(df_P0, df_P1, df_P2) %>%
  mutate(id = as.character(id)) %>%
  distinct(id, .keep_all = TRUE) %>%
  filter(!es_retweet) %>%
  arrange(fecha_dt)

write_csv(df_consolidado, ARCHIVO_CONSOLIDADO)

cat("\n╔══════════════════════════════════════════════════════════╗\n")
cat("║              RECOLECCIÓN COMPLETADA                     ║\n")
cat("╚══════════════════════════════════════════════════════════╝\n")
cat(sprintf("Total tweets      : %s\n", format(nrow(df_consolidado), big.mark = ",")))
cat(sprintf("  P0 Pre-pres.    : %s\n", format(nrow(df_P0), big.mark = ",")))
cat(sprintf("  P1 Presidencia1 : %s\n", format(nrow(df_P1), big.mark = ",")))
cat(sprintf("  P2 Presidencia2 : %s\n", format(nrow(df_P2), big.mark = ",")))
cat(sprintf("Archivo final     : %s\n", ARCHIVO_CONSOLIDADO))
cat(sprintf("Finalizado        : %s\n", format(Sys.time(), "%Y-%m-%d %H:%M:%S")))